package demoMod.gdxform.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import demoMod.gdxform.abstracts.Container;
import demoMod.gdxform.core.AbstractAction;
import demoMod.gdxform.core.ActionManager;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.helpers.FontHelper;
import demoMod.gdxform.interfaces.Element;
import demoMod.gdxform.interfaces.KeyboardEventSubscriber;
import demoMod.gdxform.interfaces.MouseEventSubscriber;
import demoMod.gdxform.interfaces.ResizeEventSubscriber;

public class GTextField implements Element, MouseEventSubscriber, KeyboardEventSubscriber, ResizeEventSubscriber {
    private Container<? extends Element> parent;
    private float x;
    private float y;
    private float width;
    private float height;
    private final BitmapFont font;
    private boolean enabled = true;
    private boolean selectionMode = false;
    private int selectionStartIndex;
    private Color backgroundColor = Color.WHITE.cpy();
    private Texture bgTexture;
    private Texture cursorTexture;
    private Texture selectedBgTexture;
    private Color textColor = Color.BLACK.cpy();
    private Color selectedBgColor = Color.BLUE.cpy();
    private Color selectedTextColor = Color.WHITE.cpy();
    private Color placeholderColor = Color.GRAY.cpy();
    private final Color cursorColor = Color.WHITE.cpy();
    private AbstractAction cursorAnimation;

    protected boolean clicked = false;

    private int windowIndex = 0;
    private int cursorIndex = 0;

    private boolean visible = true;
    private String id = "";
    private final StringBuilder text;
    private String placeholder = "";
    private static final GlyphLayout layout = FontHelper.layout;
    public static String cutText = "剪切";
    public static String copyText = "复制";
    public static String pasteText = "粘贴";
    public static String selectAllText = "全选";
    protected ActionManager actionManager;

    public GTextField(float x, float y, float width, float height, BitmapFont font) {
        setX(x);
        setY(y);
        setWidth(width);
        setHeight(height);
        refreshBgTexture();
        refreshSelectedBgTexture();
        actionManager = new ActionManager();
        this.font = font;
        this.text = new StringBuilder();
        Pixmap cursor = new Pixmap((int) font.getSpaceWidth(), (int) height, Pixmap.Format.RGBA8888);
        cursor.setColor(Color.BLACK.cpy());
        cursor.fillRectangle(0, (int) (height * 0.2F), 2, (int) (height * 0.6F));
        this.cursorTexture = new Texture(cursor);
        cursorAnimation = new AbstractAction.Builder().setDuration(0.5F).setActionBody(duration -> {
            if (duration < 0 && FormManager.getInstance().getEventHooks().getCurrentFocusedElement() == GTextField.this) {
                cursorColor.a = 1.0F - cursorColor.a;
                cursorAnimation.setDuration(0.5F);
                actionManager.addToSequenceBot(cursorAnimation);
            }
            return duration < 0;
        }).build();
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public void update() {
        actionManager.update();
    }

    @Override
    public void render(SpriteBatch sb) {
        if (!this.visible) return;
        sb.setColor(Color.WHITE);
        if (this.bgTexture != null) {
            sb.draw(this.bgTexture, getX(true), getY(true), getWidth(), getHeight());
        }
        font.setColor(this.textColor);
        String s = text.substring(windowIndex, getIndexForLastCharInWindow());
        if (s.length() > 0) {
            font.draw(sb, s, getX(true), getY(true) + (getHeight() + font.getLineHeight() / 2.0F) / 2.0F, 0, s.length(), getWidth(), 8, false);
        } else if (FormManager.getInstance().getEventHooks().getCurrentFocusedElement() != this) {
            int index = this.placeholder.length();
            layout.setText(font, this.placeholder.substring(0, index));
            while (layout.width > getWidth() && index > 0) {
                layout.setText(font, this.placeholder.substring(0, --index));
            }
            String placeholderString = this.placeholder.substring(0, index);
            font.setColor(this.placeholderColor);
            font.draw(sb, placeholderString, getX(true), getY(true) + (getHeight() + font.getLineHeight() / 2.0F) / 2.0F, 0, placeholderString.length(), getWidth(), 8, false);
        }
        if (FormManager.getInstance().getEventHooks().getCurrentFocusedElement() == this) {
            if (selectionMode) {
                int[] range = getSelectedRange();
                sb.setColor(Color.WHITE);
                sb.draw(this.selectedBgTexture,
                        getXPositionAt(Math.max(range[0], windowIndex)), getY(true),
                        getXPositionAt(Math.min(range[1], getIndexForLastCharInWindow())) - getXPositionAt(Math.max(range[0], windowIndex)), getHeight());

                font.setColor(this.selectedTextColor);
                String selectedText = getSelectedText();
                selectedText = selectedText.substring(Math.max(0, windowIndex - range[0]), Math.min(selectedText.length(), getIndexForLastCharInWindow() - range[0]));
                font.draw(sb, selectedText,
                        getXPositionAt(Math.max(range[0], windowIndex)), getY(true) + (getHeight() + font.getLineHeight() / 2.0F) / 2.0F,
                        0, selectedText.length(), getWidth(), 8, false);
            }
            sb.setColor(this.cursorColor);
            sb.draw(this.cursorTexture, getX(true) + getDistBetweenChars(windowIndex, cursorIndex), getY(true));
        }
    }

    public void setCursorTexture(Texture cursorTexture) {
        if (this.cursorTexture != null) {
            this.cursorTexture.dispose();
        }
        this.cursorTexture = cursorTexture;
    }

    private float getXPositionAt(int index) {
        return getX(true) + getDistBetweenChars(windowIndex, index);
    }

    private float getDistBetweenChars(int index1, int index2) {
        if (index1 == index2) return 0;
        float multi = 1.0F;
        if (index1 > index2) {
            int t = index1;
            index1 = index2;
            index2 = t;
            multi = -1.0F;
        }
        layout.setText(font, text.substring(index1, Math.min(index2, text.length())));
        return layout.width * multi;
    }

    public String getText() {
        return text.toString();
    }

    public String getSelectedText() {
        if (selectionMode) {
            int[] range = getSelectedRange();
            return text.substring(range[0], range[1]);
        }
        return "";
    }

    public void setSelectedTextColor(Color selectedTextColor) {
        this.selectedTextColor = selectedTextColor;
    }

    public void setPlaceholderColor(Color placeholderColor) {
        this.placeholderColor = placeholderColor;
    }

    private int[] getSelectedRange() {
        if (selectionMode) {
            return new int[]{Math.min(cursorIndex, selectionStartIndex), Math.max(cursorIndex, selectionStartIndex)};
        }
        return new int[]{cursorIndex, cursorIndex};
    }

    public void setText(String text) {
        this.text.delete(0, this.text.length());
        this.text.append(text);
        this.cursorIndex = 0;
        onCursorIndexChange();
    }

    public void append(Object o) {
        this.text.append(o);
    }

    public void insert(Object o) {
        String stringToInsert = o.toString();
        this.text.insert(this.cursorIndex, stringToInsert);
        this.cursorIndex += stringToInsert.length();
        onCursorIndexChange();
    }

    public String getPlaceholder() {
        return placeholder;
    }

    public void setPlaceholder(String placeholder) {
        this.placeholder = placeholder;
    }

    public Color getTextColor() {
        return textColor;
    }

    public void setTextColor(Color textColor) {
        this.textColor = textColor;
    }

    @Override
    public Container<? extends Element> getParent() {
        return this.parent;
    }

    @Override
    public void setParent(Container<? extends Element> parent) {
        this.parent = parent;
    }

    public float getX(boolean isAbsolute) {
        return isAbsolute && getParent() != null ? getParent().getX(true) + x : x;
    }

    @Override
    public float getY(boolean isAbsolute) {
        return isAbsolute && getParent() != null ? getParent().getY(true) + y : y;
    }

    @Override
    public void setX(float x) {
        this.x = x;
    }

    @Override
    public void setY(float y) {
        this.y = y;
    }

    @Override
    public float getWidth() {
        return this.width;
    }

    @Override
    public float getHeight() {
        return this.height;
    }

    @Override
    public void setWidth(float width) {
        this.width = width;
    }

    @Override
    public void setHeight(float height) {
        this.height = height;
    }

    @Override
    public Color getBackground() {
        return this.backgroundColor;
    }

    @Override
    public void setBackground(Color background) {
        if (!background.equals(this.backgroundColor)) {
            this.backgroundColor = background;
            refreshBgTexture();
        }
    }

    public void setBackground(Texture bgTexture) {
        if (this.bgTexture != null) {
            this.bgTexture.dispose();
        }
        this.bgTexture = bgTexture;
    }

    public void setSelectedBgTexture(Texture selectedBgTexture) {
        if (this.selectedBgTexture != null) {
            this.selectedBgTexture.dispose();
        }
        this.selectedBgTexture = selectedBgTexture;
    }

    public void setSelectedBgColor(Color selectedBgColor) {
        if (!selectedBgColor.equals(this.selectedBgColor)) {
            this.selectedBgColor = selectedBgColor;
            refreshSelectedBgTexture();
        }
    }

    private void refreshBgTexture() {
        if (this.bgTexture != null) {
            this.bgTexture.dispose();
        }
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(this.backgroundColor);
        pixmap.fill();
        this.bgTexture = new Texture(pixmap);
    }

    private void refreshSelectedBgTexture() {
        if (this.selectedBgTexture != null) {
            this.selectedBgTexture.dispose();
        }
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(this.selectedBgColor);
        pixmap.fill();
        this.selectedBgTexture = new Texture(pixmap);
    }

    public void setCursorAnimation(AbstractAction cursorAnimation) {
        this.cursorAnimation = cursorAnimation;
    }

    @Override
    public boolean isResizable() {
        return false;
    }

    @Override
    public void setResizable(boolean resizable) {

    }

    @Override
    public boolean enabled() {
        return enabled;
    }

    @Override
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public boolean visible() {
        return visible;
    }

    @Override
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    @Override
    public void activate() {
        actionManager.addToSequenceBot(cursorAnimation);
    }

    @Override
    public void deactivate() {

    }

    @Override
    public void dispose() {
        if (bgTexture != null) {
            bgTexture.dispose();
        }
        if (cursorTexture != null) {
            cursorTexture.dispose();
        }
        if (selectedBgTexture != null) {
            selectedBgTexture.dispose();
        }
    }

    protected void copy() {
        if (selectionMode) {
            Gdx.app.getClipboard().setContents(this.getSelectedText());
        }
    }

    protected void selectAll() {
        cursorIndex = text.length();
        selectionStartIndex = 0;
        selectionMode = true;
        onCursorIndexChange();
    }

    protected void cut() {
        copy();
        if (selectionMode) {
            deleteLogic();
        }
    }

    protected void paste() {
        if (selectionMode) {
            deleteLogic();
        }
        insert(Gdx.app.getClipboard().getContents());
    }

    @Override
    public boolean keyDown(int keyCode) {
        if (FormManager.getInstance().getEventHooks().getCurrentFocusedElement() == this && enabled()) {
            if (keyCode == Input.Keys.C && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
                copy();
                return true;
            }
            if (keyCode == Input.Keys.A && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
                selectAll();
                return true;
            }
            if (keyCode == Input.Keys.X && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
                cut();
                return true;
            }
            if (keyCode == Input.Keys.V && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
                paste();
                return true;
            }
            if (keyCode == Input.Keys.LEFT) {
                if (cursorIndex > 0) {
                    cursorIndex--;
                    onCursorIndexChange();
                }
                selectionMode = false;
            } else if (keyCode == Input.Keys.RIGHT) {
                if (cursorIndex < text.length()) {
                    cursorIndex++;
                    onCursorIndexChange();
                }
                selectionMode = false;
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean keyUp(int keyCode) {
        return false;
    }

    private String charFilter = "[a-zA-Z0-9,./;'\\[\\]\\\\<>?:\"{}|\\s`~\\-=!@#$%^&*()_+]";

    @Override
    public boolean keyTyped(char key) {
        if (FormManager.getInstance().getEventHooks().getCurrentFocusedElement() == this && enabled()) {
            String s = String.valueOf(key);
            if (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT)) {
                return true;
            }
            if ((s.matches(charFilter) && key != 13 && key != 9) || (key >= 19968 && key <= 40869)) { //回车和tab、中文
                if (selectionMode) {
                    deleteLogic();
                }
                text.insert(cursorIndex, key);
                cursorIndex++;
                onCursorIndexChange();
                return true;
            } else if (key == 8 || key == 127) { //backspace和delete
                deleteLogic();
                return true;
            }
        }
        return false;
    }

    private void deleteLogic() {
        if (cursorIndex >= 0) {
            if (selectionMode) {
                int[] range = getSelectedRange();
                text.delete(range[0], range[1]);
                cursorIndex = range[0];
                selectionMode = false;
                onCursorIndexChange();
            } else if (cursorIndex > 0) {
                cursorIndex--;
                text.deleteCharAt(cursorIndex);
                onCursorIndexChange();
            }
        }
    }

    private void onCursorIndexChange() {
        float currDist = getDistBetweenChars(windowIndex, cursorIndex);
        while (currDist > getWidth()) {
            currDist = getDistBetweenChars(++windowIndex, cursorIndex);
        }
        while (currDist < 0) {
            currDist = getDistBetweenChars(--windowIndex, cursorIndex);
        }
        currDist = getDistBetweenChars(windowIndex, text.length());
        while (currDist < getWidth() && windowIndex > 0) {
            currDist = getDistBetweenChars(--windowIndex, cursorIndex);
        }
    }

    /**
     * 获得窗口中最后一个字符的下标
     * @return 窗口中最后一个字符的下标
     */
    private int getIndexForLastCharInWindow() {
        int ret = windowIndex;
        float dist = getDistBetweenChars(windowIndex, ret);
        while (dist < getWidth() && ret < text.length()) {
            dist = getDistBetweenChars(windowIndex, ++ret);
        }
        return ret;
    }

    /**
     * 根据字符所在横坐标获取对应字符的下标(仅限显示窗口内的字符)
     * @param screenX 横坐标
     * @return 对应字符的下标(仅限显示窗口内的字符)
     */
    private int getIndexByScreenX(int screenX) {
        int index = getIndexForLastCharInWindow();
        float dist = getDistBetweenChars(windowIndex, index);
        while (getX(true) + dist - font.getSpaceWidth() / 2.0F >= screenX && index > windowIndex) {
            dist = getDistBetweenChars(windowIndex, --index);
        }
        return index;
    }

    public String getCharFilter() {
        return charFilter;
    }

    public void setCharFilter(String charFilter) {
        this.charFilter = charFilter;
    }

    @Override
    public boolean clickDown(int screenX, int screenY, int button) {
        if (!this.visible()) return false;
        clicked = screenX >= getX(true) && screenX <= getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - screenY >= getY(true) && Gdx.graphics.getHeight() - screenY <= getY(true) + getHeight();
        if (clicked && enabled()) {
            FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);
            if (button == Input.Buttons.LEFT) {
                selectionMode = false;
                cursorIndex = getIndexByScreenX(screenX);
                selectionStartIndex = cursorIndex;
                onCursorIndexChange();
            } else if (button == Input.Buttons.RIGHT) {
                GMenuList menuList = getPopupMenu();
                FormManager.getInstance().addContainer(menuList);
                FormManager.getInstance().moveToTop(menuList);
            }
        }
        return clicked;
    }

    protected GMenuList getPopupMenu() {
        GMenuList menuList = new GMenuList();
        menuList.setWidth(100.0F);
        menuList.setX(Gdx.input.getX());
        menuList.setY(Gdx.graphics.getHeight() - Gdx.input.getY());

        GMenuItem cut = new GMenuItem(menuList.getWidth(), menuList.getMenuItemHeight(), cutText, this.font) {
            @Override
            public void onClick() {
                cut();
            }
        };
        GMenuItem copy = new GMenuItem(menuList.getWidth(), menuList.getMenuItemHeight(), copyText, this.font) {
            @Override
            public void onClick() {
                copy();
            }
        };
        GMenuItem paste = new GMenuItem(menuList.getWidth(), menuList.getMenuItemHeight(), pasteText, this.font) {
            @Override
            public void onClick() {
                paste();
            }
        };
        GMenuItem selectAll = new GMenuItem(menuList.getWidth(), menuList.getMenuItemHeight(), selectAllText, this.font) {
            @Override
            public void onClick() {
                selectAll();
            }
        };
        selectAll.setSplitLine(false);

        menuList.addElement(cut);
        menuList.addElement(copy);
        menuList.addElement(paste);
        menuList.addElement(selectAll);

        return menuList;
    }

    @Override
    public boolean clickUp(int screenX, int screenY, int button) {
        if (clicked) {
            clicked = false;
            return true;
        }
        return false;
    }

    @Override
    public boolean mouseDragged(int screenX, int screenY) {
        if (clicked && enabled() && FormManager.getInstance().getEventHooks().getCurrentFocusedElement() == this) {
            selectionMode = true;
            if (screenX < getX(true)) {
                if (cursorIndex > 0) {
                    cursorIndex--;
                }
            } else if (screenX < getX(true) + getWidth()) {
                cursorIndex = getIndexByScreenX(screenX);
            } else {
                if (cursorIndex < text.length()) {
                    cursorIndex++;
                }
            }
            onCursorIndexChange();
            return true;
        }
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        return false;
    }

    @Override
    public boolean moveToElementBorder(Element element) {
        return false;
    }

    @Override
    public void resize(float deltaX, float deltaY, boolean fromParent) {

    }

    @Override
    public boolean hasFocus() {
        return true;
    }
}
